package zombiegame;

import graphics.Field;
import graphics.Location;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

import zombiegame.Simulator.*;

/**
 * Human class, derives from Character
 * 
 * @author pylaffon
 * 
 */
public class Human extends Character {

    // The age at which a human can start to breed.
    private static final int BREEDING_AGE = 15;
    // The age to which a human can live.
    private static final int MAX_AGE = 90;
    // The likelihood of a human breeding.
    private static final double BREEDING_PROBABILITY = 0.25;
    // The maximum number of births.
    private static final int MAX_LITTER_SIZE = 4;
    // A shared random number generator to control breeding.
    private static final Random rand = Randomizer.getRandom();

    private boolean hasBeenBitten; // false, until a vampire bites this human
    private boolean hasBeenKilled; // false, until a zombie kills this human
    private int turnsSinceLastMeal; // the human will lose health if he's too
    // hungry
    private int age; // Age of the human
    private List<Weapon> weapon;

    /**
     * Constructor of Human class. At the beginning of the game, humans just had
     * dinner, and have not been bitten yet.
     * 
     * @param name
     *            name of the character
     * @param healthPoints
     *            initial HP
     */
    public Human(String name, int healthPoints, Field field, Location location) {
        super(name, healthPoints, field, location, TypeCharacter.Human);
        hasBeenBitten = false;
        hasBeenKilled = false;
        turnsSinceLastMeal = 0;
        age = 10;
        weapon = new ArrayList<Weapon>();
        this.weapon.add(new Shotgun());
        this.weapon.add(new LiquidNitrogen());
        this.weapon.add(new WoodenStake());
    }

    // Accessors and mutators
    public boolean getHasBeenBitten() {
        return hasBeenBitten;
    }

    public boolean getHasBeenKilled() {
        return hasBeenKilled;
    }

    public void setHasBeenBitten(boolean hasBeenBitten) {
        this.hasBeenBitten = hasBeenBitten;
    }

    public void setHasBeenKilled(boolean hasBeenKilled) {
        this.hasBeenKilled = hasBeenKilled;
    }

    /**
     * Method triggered on each character at the end of each turn.
     */
    public void endOfTurn(List<Human> newHumans, List<Vampire> newVampires) {

        incrementAge();
        if (alive) {

            // // Increment the number of turns since the last time the human
            // ate
            // turnsSinceLastMeal++;
            // // If the human is too hungry, he will lose health...
            // if (turnsSinceLastMeal > 3) {
            // healthPoints -= 2;
            // }
            giveBirth(newHumans);
            if (this.hasBeenBitten) {
                newVampires.add(this.turnIntoVampire());
            } else {

                //Location newLocation = findFood(location);
                Location newLocation = findEnnemi(location);
                if (newLocation != null) {
                    Character c = (Character) field.getObjectAt(newLocation);
                    encounterCharacter(c);
                    newLocation = null;
                } else {
                    // No ennemi found - try to move to a free location.
                    newLocation = field.freeAdjacentLocation(location);
                }
                // See if it was possible to move.
                if (newLocation != null) {
                    setLocation(newLocation);
                } else {
                    // Overcrowding.
                    setDead();
                }
            }
        }

    }

    /**
     * Increase the age. This could result in the rabbit's death.
     */
    private void incrementAge() {
        age++;
        if (age > MAX_AGE) {
            setDead();
        }
    }

    /**
     * Tell the fox to look for rabbits adjacent to its current location. Only
     * the first live rabbit is eaten.
     * 
     * @param location
     *            Where in the field it is located.
     * @return Where food was found, or null if it wasn't.
     */
    private Location findFood(Location location) {
        // List<Location> adjacent = field.adjacentLocations(location);
        // Iterator<Location> it = adjacent.iterator();
        // while (it.hasNext()) {
        // Location where = it.next();
        // Object object = field.getObjectAt(where);
        // if (object == null) {
        // Rabbit rabbit = (Rabbit) character;
        // if (rabbit.isAlive()) {
        // rabbit.setDead();
        // foodLevel = RABBIT_FOOD_VALUE;
        // // Remove the dead rabbit from the field.
        // return where;
        // }
        // }
        return null;
    }

    /**
     * Tell the fox to look for rabbits adjacent to its current location. Only
     * the first live rabbit is eaten.
     * 
     * @param location
     *            Where in the field it is located.
     * @return Where food was found, or null if it wasn't.
     */
    private Location findEnnemi(Location location) {
        List<Location> adjacent = field.adjacentLocations(location);
        Iterator<Location> it = adjacent.iterator();
        while (it.hasNext()) {
            Location where = it.next();
            Object object = field.getObjectAt(where);
            if (object instanceof Character) {
                Character character = (Character) object;
                if (character.getType() == TypeCharacter.Vampire || character.getType() == TypeCharacter.Zombie) {
                    return where;
                }
            }
        }
        return null;
    }

    /**
     * Method triggered when the human described by the current object meets
     * another character, and does something to him (for example, attack).
     * 
     * @param c
     *            the other character that this character meets
     */
    public void encounterCharacter(Character c) {
        int i = 0;
        // say("Go away!");
        while (i < weapon.size()) {
            System.out.println("recherche d'une arme");
            if (c.getType().equals(weapon.get(i).getTarget()) && weapon.get(i).available()) {
                this.attack(weapon.get(i), c);
            } else
                i++;
        }
    }

    public void attack(Weapon w, Character c) {
        w.use(c);
    }

    /**
     * Transform this human who has been bitten, into a blood-thirsty vampire.
     * 
     * @return a new object of class Vampire, with the same name and
     *         healthpoints as this human; the new vampire is immediately
     *         thirsty
     */
    public Vampire turnIntoVampire() {
        Vampire v = new Vampire(name, healthPoints, this.getField(), this.getLocation());
        this.setDead();
        return v;
    }

    /**
     * Transform this human who has been killed by a zombie, into a zombie.
     * 
     * @return a new object of class Zombie, with the same name and healthpoints
     *         as this human; the new vampire is immediately thirsty
     */
    public Zombie turnIntoZombie() {
        Zombie z = new Zombie(name, 30, this.getField(), this.getLocation());
        this.setDead();
        return z;
    }

    /**
     * Check whether or not this human is to give birth at this step. New births
     * will be made into free adjacent locations.
     * 
     * @param newHumans
     *            A list to add newly born humans to.
     */
    private void giveBirth(List<Human> newHumans) {
        // New humans are born into adjacent locations.
        // Get a list of adjacent free locations.
        List<Location> free = field.getFreeAdjacentLocations(location);
        int births = breed();
        for (int b = 0; b < births && free.size() > 0; b++) {
            Location loc = free.remove(0);
            Human young = new Human("", 100, field, loc);
            newHumans.add(young);
        }
    }

    /**
     * Generate a number representing the number of births, if it can breed.
     * 
     * @return The number of births (may be zero).
     */
    private int breed() {
        int births = 0;
        if (canBreed() && rand.nextDouble() <= BREEDING_PROBABILITY) {
            births = rand.nextInt(MAX_LITTER_SIZE) + 1;
        }
        return births;
    }

    /**
     * A rabbit can breed if it has reached the breeding age.
     * 
     * @return true if the rabbit can breed, false otherwise.
     */
    private boolean canBreed() {
        return age >= BREEDING_AGE;
    }
}
